Skip to content

fix: aggregate dynamic filtering with unsupported expressions - #24817

Merged
kosiew merged 3 commits into
apache:mainfrom
lyne7-sc:fix/aggregate-dynamic-filter-unsupported-expression
Sep 10, 2026
Merged

kosiew merged 3 commits into
apache:mainfrom
lyne7-sc:fix/aggregate-dynamic-filter-unsupported-expression

Conversation

@lyne7-sc

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Aggregate dynamic filtering currently ignores unsupported expressions while still generating a shared scan predicate from supported aggregates. This can prune rows required by the unsupported aggregate and produce incorrect results.

What changes are included in this PR?

  • Disable aggregate dynamic filtering when any aggregate expression is unsupported.
  • Document that aggregate dynamic filtering requires every aggregate expression to be supported.
  • Update the existing sqllogictest to verify that no dynamic filter is produced for mixed supported and unsupported expressions.

What is the testing strategy for this PR?

Yes. Updated the existing sqllogictest case in push_down_filter_regression.slt.

Are there any user-facing changes?

Yes. This fixes potentially incorrect query results. No API changes.

@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) physical-plan Changes to the physical-plan crate labels Aug 31, 2026
@codecov-commenter

codecov-commenter commented Aug 31, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 81.71%. Comparing base (4b8ad88) to head (62cfeef).
⚠️ Report is 104 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/physical-plan/src/aggregates/mod.rs 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #24817      +/-   ##
==========================================
+ Coverage   81.53%   81.71%   +0.18%     
==========================================
  Files        1123     1127       +4     
  Lines      406042   416238   +10196     
  Branches   406042   416238   +10196     
==========================================
+ Hits       331059   340147    +9088     
- Misses      55621    56100     +479     
- Partials    19362    19991     +629     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kosiew kosiew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lyne7-sc,

Thanks for working on this. The all-or-nothing eligibility check makes sense and addresses the correctness issue with mixed aggregate expressions.

I have one blocking comment around the regression coverage. The current test confirms that the dynamic filter is no longer present in the plan, but I think we should also verify the query result directly so that the test protects the user-visible behavior this change is fixing.

I also left one small, non-blocking naming suggestion.

# -> DynamicFilter [ a < 1 OR a > 8 OR b > 12 ] (MIN(c+1) dropped as unsupported)
# No dynamic filter because not every aggregate has a safe predicate.
query TT
EXPLAIN ANALYZE SELECT MIN(a), MAX(a), MAX(b), MIN(c + 1) FROM agg_dyn_mixed;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add an execution-result assertion here as well? The current EXPLAIN ANALYZE assertion confirms that the dynamic filter is absent, but it does not directly verify the correctness issue this change is intended to fix: the unsupported aggregate still needs to see every relevant row.

For this fixture, the expected result for MIN(a), MAX(a), MAX(b), MIN(c + 1) is 1, 8, 12, 71.

It would also be good to make the fixture or scan order deterministic enough that the old behavior actually prunes the row needed by MIN(c + 1). With the current two-file layout, that can depend on which file publishes its bounds first. Ideally, this regression test should fail with the old implementation because it produces the wrong result, rather than only because the plan contains a dynamic filter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've added a result check and adjusted the test data so the old implementation produces the wrong result. The test passes with the fix.

/// is enabled only when every aggregate expression is supported, so this vector
/// contains an entry for every accumulator. For example, `min(a), max(b)` produces
/// entries for `min(a)` and `max(b)`.
supported_accumulators_info: Vec<PerAccumulatorDynFilter>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small naming suggestion, not blocking: would it make sense to rename supported_accumulators_info to something like accumulator_dyn_filter_info? With the new all-or-nothing behavior, this contains metadata for every aggregate whenever dynamic filtering is enabled, so dropping supported might make that invariant a little clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense to me. renamed to accumulator_dyn_filter_info.

@kosiew kosiew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lyne7-sc, thanks for addressing the earlier feedback. This looks good to me now.

The aggregate dynamic filter is correctly disabled when any aggregate is not a plain-column MIN or MAX, which avoids filtering out rows needed by unsupported aggregates. The rename to accumulator_dyn_filter_info is also applied consistently across the execution and validation paths.

I also appreciate the deterministic parquet regression coverage. The result assertion makes the failure mode clear, and the fixture demonstrates that the old partial-filter behavior would incorrectly produce MIN(c + 1) = 101 instead of 71.

No further comments from me. Thanks for the updates!

@kosiew

kosiew commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🚀
@lyne7-sc
Thank you for your contribution.

@kosiew
kosiew added this pull request to the merge queue Sep 9, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Sep 9, 2026
@kosiew
kosiew added this pull request to the merge queue Sep 10, 2026
Merged via the queue into apache:main with commit b3cb365 Sep 10, 2026
41 checks passed
discord9 added a commit to GreptimeTeam/datafusion that referenced this pull request Sep 10, 2026
Adapted from apache#24817, commit b3cb365. Retain the equivalent 53.1.0 eligibility guard without unrelated metadata renames.

Signed-off-by: discord9 <discord9@163.com>
discord9 added a commit to discord9/datafusion that referenced this pull request Sep 10, 2026
Backport apache#24817 (b3cb365) to DataFusion 55.

Signed-off-by: discord9 <discord9@163.com>
@lyne7-sc
lyne7-sc deleted the fix/aggregate-dynamic-filter-unsupported-expression branch September 13, 2026 05:02
discord9 added a commit to GreptimeTeam/datafusion that referenced this pull request Sep 23, 2026
Backport apache#24817 (b3cb365) to DataFusion 55.

Signed-off-by: discord9 <discord9@163.com>
lukekim pushed a commit to spiceai/datafusion that referenced this pull request Sep 27, 2026
…#24817)

## Which issue does this PR close?

<!--
We generally require a GitHub issue to be filed for all bug fixes and
enhancements and this helps us generate change logs for our releases.
You can link an issue to this PR using the GitHub syntax. For example
`Closes #123` indicates that this PR will close issue #123.
-->

- Closes apache#24816.

## Rationale for this change

<!--
Why are you proposing this change? If this is already explained clearly
in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand
your changes and offer better suggestions for fixes.

Please explain the problem you are trying to solve in terms of the
user-visible
behavior, rather than the implementation.

For example, "The code in `foo.rs` doesn't handle nulls" is a symptom of
the
implementation. "COUNT(DISTINCT) returns wrong results when the column
contains
nulls" is the user-visible problem.
-->

Aggregate dynamic filtering currently ignores unsupported expressions
while still generating a shared scan predicate from supported
aggregates. This can prune rows required by the unsupported aggregate
and produce incorrect results.

## What changes are included in this PR?

<!--
There is no need to duplicate the description in the issue here, but it
is sometimes worth providing a summary of the individual changes in this
PR.
-->

- Disable aggregate dynamic filtering when any aggregate expression is
unsupported.
- Document that aggregate dynamic filtering requires every aggregate
expression to be supported.
- Update the existing sqllogictest to verify that no dynamic filter is
produced for mixed supported and unsupported expressions.

## What is the testing strategy for this PR?

<!--
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code

Briefly describe how this PR is tested, and point to the specific tests
you added. For example: 'This new feature is covered by the
`sqllogictest` cases added in `foo.slt`'.

If this PR does not add tests, explain why. For example, if the change
is already covered by existing tests, please mention it.

You should also check the `codecov` bot reply on this PR to confirm the
changed code is exercised.
-->

Yes. Updated the existing sqllogictest case in
`push_down_filter_regression.slt`.

## Are there any user-facing changes?

<!--
If there are user-facing changes then we may require documentation to be
updated before approving the PR.

If there are any breaking changes to public APIs, please add the `api
change` label.
-->

Yes. This fixes potentially incorrect query results. No API changes.

(cherry picked from commit b3cb365)

Conflicts resolved when applying to spiceai-54 (DataFusion 54.1):
- datafusion/physical-plan/src/aggregates/mod.rs: the base spells the
  cols_for_dynamic_filter check as debug_assert!(a == b); applied only the
  upstream rename (supported_accumulators_info -> accumulator_dyn_filter_info)
  to that line.
- datafusion/sqllogictest/test_files/push_down_filter_regression.slt: the base
  carries an older version of the mixed-expressions section; replaced it with
  the upstream section verbatim. The upstream aggregate_stream.rs hunks apply
  to aggregates/no_grouping.rs, its name on 54.1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Aggregate dynamic filter can prune rows required by unsupported expressions

3 participants